package dnl.confij;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * Encapsulates a configuration section which serves as a container for
 * configuration parameters.<br>
 * The values of the section a held as <code>String</code>s but may be
 * retrieved as any type.
 * 
 * @author Daniel Or
 * 
 */
public class ConfigSection {
	private Map<String, ConfigParam> paramsMap = new HashMap<String, ConfigParam>();

	private String name;

	/**
	 * Constructs a <code>ConfigSection</code> with the given name.
	 * 
	 * @param name
	 *            a section name.
	 */
	public ConfigSection(String name) {
		this.name = name;
	}

	// -------------------------------------------------------

	/**
	 * Gets the name of this section. This name identifies the section uniquely
	 * in the context of it's parent configuraion.
	 * 
	 * @return this section's name.
	 */
	public String getName() {
		return name;
	}

	/**
	 * Gets the value that corresponds to the given parameter name. In order to
	 * get a value of a specific type use the other accessors in this class.
	 * 
	 * @param paramName
	 *            the parameter's name
	 * @return the <code>String</code>ed representation of the value.
	 */
	public String getParamValue(String paramName) {
		ConfigParam configParam = paramsMap.get(paramName);
		if(configParam == null){
			return null;
		}
		return configParam.getValue();	
	}

	/**
	 * Sets the given parameter's value using the value's <code>String</code>ed
	 * representation. For convenience, use specific type accessors that appear
	 * in this class.
	 * 
	 * @param paramName
	 *            the parameter's name
	 * @param value
	 *            the <code>String</code>ed representation of the value.
	 */
	public void setParam(String paramName, String value, ParameterType parameterType) {
		ConfigParam configParam = paramsMap.get(paramName);
		if(configParam == null){
			configParam =  new ConfigParam(paramName, parameterType);
			paramsMap.put(paramName, configParam);
		}
		configParam.setValue(value);
	}

	/**
	 * Assigns a string value to the given parameter name.
	 * 
	 * @param paramName
	 *            the parameter's name.
	 * @param value
	 *            the value.
	 */
	public void setParamValue(String paramName, String value) {
		this.setParam(paramName, value, ParameterType.STRING);
	}

	/**
	 * Assigns a boolean value to the given parameter name.
	 * 
	 * @param paramName
	 *            the parameter's name.
	 * @param value
	 *            the value.
	 */
	public void setParamValue(String paramName, boolean value) {
		this.setParam(paramName, Boolean.toString(value), ParameterType.BOOLEAN);
	}

	/**
	 * Assigns an integer value to the given parameter name.
	 * 
	 * @param paramName
	 *            the parameter's name.
	 * @param value
	 *            the value.
	 */
	public void setParamValue(String paramName, int value) {
		this.setParam(paramName, Integer.toString(value), ParameterType.INTEGER);
	}

	/**
	 * Assigns a double value to the given parameter name.
	 * 
	 * @param paramName
	 *            the parameter's name.
	 * @param value
	 *            the value.
	 */
	public void setParamValue(String paramName, double value) {
		this.setParam(paramName, Double.toString(value), ParameterType.DOUBLE);
	}

	/**
	 * Gets a <code>Map</code> that contains parameter names as keys and their
	 * <code>String</code>ified values as values.
	 * 
	 * @return
	 */
	public Collection<ConfigParam> getConfigParameters() {
		return paramsMap.values();
	}

	// --------------------------------------------------------

	public Integer getInt(String paramName) {
		Integer i = Integer.parseInt(getParamValue(paramName));
		return i.intValue();
	}

	public Double getDouble(String paramName) {
		Double d = Double.parseDouble(getParamValue(paramName));
		return d;
	}

	public Boolean getBoolean(String paramName) {
		Boolean b = Boolean.parseBoolean(getParamValue(paramName));
		return b;
	}

	public String getString(String paramName) {
		return getParamValue(paramName);
	}

	/**
	 * Gets an <code>Object</code> that corresponds to te given parameter
	 * based on the existence of a <code>ValueConverter</code>.
	 * 
	 * @param paramName
	 *            the parameter's name.
	 * @param clazz
	 *            the <code>Class</code> of the desired returned type.
	 * @return an <code>Object</code> that corresponds to te given parameter
	 *         based on the existence of a <code>ValueConverter</code>. If no
	 *         converter exists, returns the raw <code>String</code>ed value.
	 */
	public Object get(String paramName, Class<?> clazz) {
		String stringVal = getParamValue(paramName);
		ValueConverter converter = ConvertersRegistry.getInstance().getConverter(clazz);
		if (converter == null) {
			throw new IllegalStateException("No converter for class: " + clazz.getName());
		} else {
			return converter.parse(stringVal);
		}
	}

	/**
	 * 
	 * @param <T>
	 * @param paramName
	 * @param enumClass
	 * @return
	 */
	public <T extends Enum<T>> T getEnum(String paramName, Class<T> enumClass) {
		String pval = getParamValue(paramName);
		return Enum.valueOf(enumClass, pval);
	}

	public void setInt(String paramName, int value) {
		setParam(paramName, Integer.toString(value), ParameterType.INTEGER);
	}

	public void setDouble(String paramName, double value) {
		setParam(paramName, Double.toString(value), ParameterType.DOUBLE);
	}

	public void setBoolean(String paramName, boolean value) {
		setParam(paramName, Boolean.toString(value), ParameterType.BOOLEAN);
	}

	public void setString(String paramName, String value) {
		setParamValue(paramName, value);
	}

	/**
	 * Sets an <code>Object</code> that corresponds to te given parameter
	 * based on the existence of a <code>ValueConverter</code>. If no
	 * matching converter is found, the value will be
	 * <code>String.valueOf(value)</code>.
	 * 
	 * @param paramName
	 *            the parameter's name.
	 * @param value
	 *            the parameter's value.
	 */
	public void set(String paramName, Object value) {
		ValueConverter converter = ConvertersRegistry.getInstance().getConverter(value.getClass());
		if (converter == null) {
			setParamValue(paramName, String.valueOf(value));
		} else {
			setParamValue(paramName, converter.format(value));
		}
	}

	public <T extends Enum<T>> void setEnum(String paramName, Enum<T> value) {
		setParamValue(paramName, value.name());
	}

}
